#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include "load_crash_report.hpp"
#include "utils.hpp"

// Utility for parsing a buffer, one character at a time.
class ReadChar {
  const char* buf_;
  size_t remaining_;

public:
  ReadChar(const char* buf, size_t remaining)
    : buf_(buf), remaining_(remaining)
  {}

  bool eof() const { return (remaining_ == 0); }
  char peek() const { return buf_[0]; }

  char pop() {
    const char c = peek();
    advance();
    return c;
  }

  void advance() {
    buf_++;
    remaining_--;
  }
};

// Utility for mmap-ing a file in read-only mode.
class MmapFileRead {
  const AutoCloseFD fd_;
  const size_t buflen_;
  const AutoMunmap buf_;

  static size_t getFileSize(int fd) {
    if (fd < 0) {
      throw ErrorWithErrno("Could not open file.\n");
    }
    struct stat statbuf;
    if (fstat(fd, &statbuf) < 0) {
      throw ErrorWithErrno("fstat failed.");
    }
    if (statbuf.st_size < 0) {
      throw Error("Negative file size.");
    }
    return statbuf.st_size;
  }

public:
  MmapFileRead(const char* filename)
    : fd_(open(filename, O_RDONLY))
    , buflen_(getFileSize(fd_.get()))
    , buf_(buflen_, PROT_READ, MAP_SHARED, fd_.get(), 0)
  {}

  size_t getBuflen() const { return buflen_; }
  void* getBuf() const { return buf_.get(); }
};

CrashReportKey parseKey(ReadChar& r) {
  CrashReportKey result;
  while (!r.eof()) {
    const char c = r.pop();
    if (c == '\n') {
      throw Error("Unexpected newline in parseKey.");
    }
    if (c == ':') {
      return result;
    }
    result.push_back(c);
  }
  throw Error("Unexpected eof in parseKey.");
}

// Parse to the next newline character.
std::string parseLine(ReadChar& r) {
  std::string result;
  while (!r.eof()) {
    const char c = r.pop();
    if (c == '\n') {
      return result;
    }
    result.push_back(c);
  }
  throw Error("Unexpected eof in parseLine.");
}

CrashReportValue parseValue(ReadChar& r) {
  CrashReportValue result;
  if (r.eof()) {
    throw Error("Unexpected eof in parseValue.");
  }
  if (r.peek() == '\n') {
    r.advance();
  }
  while (!r.eof() && r.peek() == ' ') {
    r.advance();
    result.push_back(parseLine(r));
  }

  return result;
}

CrashReport parseCrashReport(ReadChar& r) {
  CrashReport result;
  while(!r.eof()) {
    if (r.peek() == ' ') {
      throw Error("Unexpected space in parseCrashReport.");
    }
    CrashReportKey key = parseKey(r);
    CrashReportValue value = parseValue(r);
    result.insert(std::pair<CrashReportKey,CrashReportValue>(key,value));
  }
  return result;
}

CrashReport loadCrashReport(const char* filename) {
  MmapFileRead mfr(filename);
  ReadChar r((const char*)mfr.getBuf(), mfr.getBuflen());
  return parseCrashReport(r);
}
